﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
using System.Windows.Forms;


namespace DBCExtractor
{
    internal struct Format
    {
        public Format(char format, string name, int skip = 0)
        {
            this.format = format;
            this.name = name;
            this.skip = skip;
        }

        internal char format;
        internal string name;
        internal int skip;
    }

    public class DBC
    {
        private BinaryReader file;
        private StreamWriter outFile;
        private XmlDocument cfgFile;

        private int records = 0;
        private int fields = 0;
        private int fieldsize = 0;
        private int stringBegin = 0;
        private int stringLength = 0;

        private List<Format> format;
        private string dbcName;

        public DBC(string path, string outPath, string cfgPath)
        {
            file = new BinaryReader(new StreamReader(path).BaseStream);
            outFile = new StreamWriter(outPath);
            cfgFile = new XmlDocument();
            cfgFile.Load(cfgPath);

            if(file.BaseStream.CanRead == false || outFile.BaseStream.CanWrite == false)
                throw new Exception();

            format = new List<Format>();

            char[] WDBC = file.ReadChars(4);
            records = file.ReadInt32();
            fields = file.ReadInt32();
            fieldsize = file.ReadInt32() / fields;
            stringBegin = fieldsize * fields * records + 20;
            stringLength = file.ReadInt32();

            parseCfg();
        }

        private void parseCfg()
        {
            XmlNodeList list = cfgFile.GetElementsByTagName("Info");
            if (list[0] == null)
                throw new Exception("No Info root element!");
            Format format = new Format();

            foreach (XmlNode node in list[0].ChildNodes)
            {
                if (node.Name == "Name")
                    this.dbcName = node.InnerText;
                else if (node.Name == "Format")
                {
                    foreach (XmlNode childNode in node.ChildNodes)
                    {
                        string name = childNode.Name;
                        if (name == "ignoreSize" && childNode.Attributes[0] != null && childNode.Attributes[0].Name == "newSize")
                        {
                            this.fieldsize = Convert.ToInt32(childNode.Attributes[0].InnerText);
                            continue;
                        }
                        else if (name == "string" || name == "charArray" && childNode.Attributes[0] != null && childNode.Attributes[0].Name == "x")
                            format = new Format(name[0], childNode.InnerText, Convert.ToInt32(childNode.Attributes[0].InnerText));
                        else if (name == "int" || name == "float" || name == "primary")
                            format = new Format(name[0], childNode.InnerText);
                        else
                            if (childNode.NodeType != XmlNodeType.Comment)
                                throw new Exception("Unexpected element in config file!");
                            else
                                continue;
                        this.format.Add(format);
                    }
                }
            }
        }

        private void parseInt()
        {
            switch (fieldsize)
            {
                case 2:
                    {
                        outFile.Write(file.ReadInt16() + "'");
                        break;
                    }
                case 4:
                    {
                        outFile.Write(file.ReadInt32() + "'");
                        break;
                    }
                case 8:
                    {
                        outFile.Write(file.ReadInt64() + "'");
                        break;
                    }
                default:
                    throw new Exception("Unexptected filedsize!");
            }
        }

        private string escape(string input)
        {
            string res = input;

            res = System.Text.RegularExpressions.Regex.Replace(input, @"[\000\010\011\012\015\032\042\047\134\140]", "\\$0");

            return res;
        }

        private string readString()
        {
            string retValue = "";

            while (true)
            {
                char chr = file.ReadChar();
                if (chr == '\0')
                    break;
                retValue += chr;
            }

            return retValue;
        }

        private void parseString(int skip)
        {
            long newPos = 0;
            long curPos = 0;
        //  int i = 0;

        //    do
        //    {
                switch (fieldsize)
                {
                    case 2:
                        {
                            newPos = file.ReadInt16();
                            break;
                        }
                    case 4:
                        {
                            newPos = file.ReadInt32();
                            break;
                        }
                    case 8:
                        {
                            newPos = file.ReadInt64();
                            break;
                        }
                    default:
                        throw new Exception("Unexptected filedsize!");
                }

        //      ++i;
        //    } while (newPos == 0 && i < skip);

            curPos = file.BaseStream.Position;
            file.BaseStream.Position = newPos + stringBegin;

            outFile.Write(escape(readString()) + "'");

            file.BaseStream.Position = curPos;
        }

        void parseCharArray(Format form)
        {
            byte[] bytes = file.ReadBytes(form.skip);
            SoapHexBinary shb = new SoapHexBinary(bytes);
            outFile.Write(shb.ToString());
        }

        void parseData()
        {
            int skip = 0;
            int j = 0;
            while (file.BaseStream.Position < stringBegin && j < records)
            {
                bool first = true;
                outFile.Write("INSERT INTO `dbc_" + dbcName + "` VALUES ('");

                int i = 0;
                while(i < format.Count)
                {
                    if (format[i].format != 'x' && --skip < 0)
                    {
                        Format form = format[i++];
                        if (!first)
                            outFile.Write(", '");

                        switch (form.format)
                        {
                            case 'p': //integers
                            case 'i':
                                {
                                    parseInt();
                                    break;
                                }
                            case 'f': //float
                                {
                                    outFile.Write(file.ReadSingle() + "'");
                                    break;
                                }
                            case 's': //string
                                {
                                    skip = form.skip;
                                    parseString(skip);
                                    break;
                                }
                            case 'c': //char array
                                {
                                    parseCharArray(form);
                                    break;
                                }
                        }

                        first = false;
                    }
                    else
                        file.BaseStream.Position += fieldsize;
                }
                ++j;
                outFile.WriteLine(");");
            }

            Console.WriteLine(j);
        }

        private void makeTable()
        {
            string primary = "";
            outFile.WriteLine("DROP TABLE IF EXISTS `dbc_" + dbcName + "`;");
            outFile.WriteLine("CREATE TABLE `dbc_" + dbcName + "` (");

            foreach (Format form in format)
            {
                if (form.format != 'x')
                {
                    switch (form.format)
                    {
                        case 'p':
                            {
                                outFile.WriteLine("   `" + form.name + "` INT unsigned NOT NULL AUTO_INCREMENT,");
                                primary = "   PRIMARY KEY(`" + form.name + "`)";
                                break;
                            }
                        case 'i':
                            {
                                outFile.WriteLine("   `" + form.name + "` INT NOT NULL,");
                                break;
                            }
                        case 'f':
                            {
                                outFile.WriteLine("   `" + form.name + "` FLOAT NOT NULL,");
                                break;
                            }
                        case 's':
                            {
                                outFile.WriteLine("   `" + form.name + "` TEXT NOT NULL,");
                                break;
                            }
                    }
                }
            }

            outFile.WriteLine(primary);
            outFile.WriteLine(") DEFAULT CHARSET=latin1;");
        }

        public void ConvertFile()
        {
            try
            {
                makeTable();

                outFile.WriteLine();

                parseData();

                file.Close();
                outFile.Close();
                cfgFile.Clone();
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message, "Exception in File " + dbcName + "!");
            }
        }
    }
}
